-- CombatM
-- Create by weism
-- 战斗模块

module("CombatM", package.seeall);

-- 战斗回合
local round = 0;

local roundAction;

local startRoundCallback;
local endRoundCallback;

function init()
    -- 注册玩家出手的处理函数
    EventMgr.removeAll("CombatM");
    EventMgr.register("CombatM", event.COMBAT_ROUND, roundAction);

    EventMgr.register("CombatM", event.LOGIN_OK, function()
        round = 0;
    end);

    -- 回合回调
    startRoundCallback = get_daemon_data("CombatM", "startRoundCallback") or {};
    endRoundCallback = get_daemon_data("CombatM", "endRoundCallback") or {};
end

function destruct()
    register_daemon_data("CombatM", "startRoundCallback", startRoundCallback);
    register_daemon_data("CombatM", "endRoundCallback", endRoundCallback);
end

-- 注册开始回合的回调
function registerStartRoundCB(key, func)
    for index, arr in pairs(startRoundCallback) do
        -- 已经注册过了，直接替换掉就行了
        if key == arr[1] then
            arr[2] = func;
            return;
        end
    end

    table.insert(startRoundCallback, { key, func, });
    table.sort(startRoundCallback, function(a, b)
        return a[1] < b[1];
    end);
end

-- 注册结束回合的回调
function registerEndRoundCB(key, func)
    for index, arr in pairs(endRoundCallback) do
        -- 已经注册过了，直接替换掉就行了
        if key == arr[1] then
            arr[2] = func;
            return;
        end
    end

    table.insert(endRoundCallback, { key, func, });
    table.sort(endRoundCallback, function(a, b)
        return a[1] < b[1];
    end);
end

-- 开始回合回调
function invokeStartRoundCallback(para)
    for _, arr in pairs(startRoundCallback) do
        if type(arr[2]) == "function" then
            catch(function() arr[2](para) end);
        end
    end
end

-- 结束回合回调
function invokeEndRoundCallback(para)
    for _, arr in pairs(endRoundCallback) do
        if type(arr[2]) == "function" then
            catch(function() arr[2](para) end);
        end
    end
end

-- 设置回合数
function setRound(layer, currRound)
    local msg = string.format("开始第%d层地牢。", layer);
    DungeonLogM.addLog(msg);

    round = currRound;
end

-- 开始新回合
function startRound()
    Profiler.funcBegin("startRound");

    local msg = string.format("开始第%d回合。", round);
    DungeonLogM.addLog(msg);

    -- 所有需要每回合尝试去触发的属性
    local triggerProps = PropM.getTriggerProps("round");

    local fetchProps = PropM.fetchProps;
    local trigger = PropM.trigger;

    -- 1. 触发玩家的一些属性
    for _, propId in ipairs(triggerProps) do
        local props = fetchProps(ME.user, propId);
        for _, prop in ipairs(props) do
            -- 触发属性
            trigger(ME.user, prop[1], prop[2]);
        end
    end

    -- 2. 触发怪物的属性
    if #triggerProps > 0 then
        local grids = DungeonM.getCurrentDungeon();
        for _, grid in pairs(grids) do
            -- 没死的怪物才需要处理
            if  grid:isOpened() and
                grid:isMonster() and
                grid.state ~= GRID_STATE_DISABLE and
                not grid.monster:isDead() and
                not FormulaM.invoke("MONSTER_IS_STOPED", grid.monster) then
                for _, propId in ipairs(triggerProps) do
                    local props = fetchProps(grid.monster, propId);

                    for _, prop in ipairs(props) do
                        -- 触发属性
                        -- 这个时候怪物可能被各种技能干死了，应再判断一下
                        if not grid.monster:isDead() and
                            not FormulaM.invoke("CAN_MONSTER_ACTIVE_ATTACK", grid.monster, prop) then
                            trigger(grid.monster, prop[1], prop[2], ME.user);
                        end
                    end
                end
            end
        end
    end

    -- 3. 触发召唤兽的属性
    if #triggerProps > 0 then
        local grids = DungeonM.getCurrentDungeon();
        for _, grid in pairs(grids) do

            -- 没死的怪物才需要处理
            if  grid:isOpened() and
                grid.summon and
                grid.state ~= GRID_STATE_DISABLE and
                not grid.summon:isDead() then
                for _, propId in ipairs(triggerProps) do
                    local props = fetchProps(grid.summon, propId);

                    for _, prop in ipairs(props) do
                        -- 触发属性
                        -- 这个时候怪物可能被各种技能干死了，应再判断一下
                        if not grid.summon:isDead() and
                            not FormulaM.invoke("CAN_MONSTER_ACTIVE_ATTACK", grid.summon, prop) then
                            trigger(grid.summon, prop[1], prop[2], ME.user);
                        end
                    end
                end
            end
        end
    end

    Profiler.funcBegin("startRound2");
    -- 开始回合的回调
    invokeStartRoundCallback();

    EventMgr.fire(event.START_ROUND, round);
    Profiler.funcEnd("startRound2");

    Profiler.funcEnd("startRound");
end

-- 回合结束
function endRound()
    Profiler.funcBegin("endRound");

    -- 把过期的prop清除
    local arr = { ME.user, };
    local grids = DungeonM.getCurrentDungeon();

    for _, grid in pairs(grids) do
        -- 没死的怪物才需要处理
        if  grid:isMonster() and
            not grid.monster:isDead() then
            table.insert(arr, grid.monster);
        end
    end

    -- 召唤兽也加进去
    arr = table.append(arr, clean_array(ME.user.dbase:queryTemp("summon_list", {})));

    -- 遍历所有对象
    for _, ob in ipairs(arr) do
        -- 属性
        local props = ob.dbase:queryTemp("prop", {});
        local modify = false;
        local cleanList = {};

        -- 遍历所有属性
        for path, propList in pairs(props) do
            if path ~= "equip" and path ~= "skill" and path ~= "lictor" then
                for index, prop in ipairs(propList) do
                    -- 判断回合数
                    if type(prop[4]) == "number" and prop[4] ~= -1 and prop[4] <= round then
                        -- 记录过期的属性
                        table.insert(cleanList, prop);
                    end
                end

                -- 移除过期属性
                if #cleanList > 0 then
                    for _, prop in ipairs(cleanList) do
                        table.removeItem(propList, prop);
                    end

                    modify = true;
                end
            end
        end

        -- 清除过期状态
        CombatStatusM.clearOverdueStatus(ob, round);

        -- 如果属性有变动，需要重整一下
        if modify then
            PropM.arrangeProps(ob);

            -- 移除过期属性
            for _, prop in pairs(cleanList) do
                PropM.clearProp(ob, prop);
            end
        end

        if modify and ob.type == OBJECT_TYPE_MONSTER then
            EventMgr.fire(event.MONSTER_INFO_UPDATED, {["pos"] = ob:getOwner(), });
        end
    end

    -- 尝试清除格子的污染状态
    arr = {};
    for pos, grid in pairs(grids) do
        -- 没翻开的格子才需要处理
        if not grid:isOpened() and grid.polluted then
            -- 污染参数
            local polluted = grid.polluted;
            if polluted[4] ~= -1 and polluted[4] <= round then
                -- 过期了
                grid.polluted = nil;
                table.insert(arr, grid:getPos());
            end

            if polluted[4] ~= -1 and polluted[4] > round then
                EventMgr.fire(event.POLLUTE_LEFT_ROUND, { ["pos"] = pos, ["left_round"] = polluted[4] - round, ["pollute_type"] = polluted[5], } );
            end
        end
    end
    EventMgr.fire(event.GRID_PURGE, arr);

    local msg = string.format("第%d回合结束。", round - 1);
    DungeonLogM.addLog(msg);

    -- 结束回合回调
    invokeEndRoundCallback();

    EventMgr.fire(event.END_ROUND);

    Profiler.funcEnd("endRound");
end

-- 获取战斗回合数
function getRound()
    return round;
end

-- 受创触发一些属性(受攻击诅咒、受攻击中毒、受攻击净化)
function woundedTrigger(source, target, damage)
    -- 是否免疫负面状态
    if FormulaM.invoke("IS_DEBUFF_IMMUNITY", source) == true then
        return;
    end

    local getPropId = PropM.getPropId;
    local trigger = PropM.trigger;

    local propId = getPropId("wounded_trigger");

    -- 1. 受攻击诅咒
    local curseId = getPropId("curse");
    trigger(target, propId, curseId, source);

    -- 2. 受攻击施毒
    local poisonedId = getPropId("poisoned");
    trigger(target, propId, poisonedId, source);

    -- 3. 受攻击施血毒
    local bloodPoisonedId = getPropId("blood_poisoned");
    trigger(target, propId, bloodPoisonedId, source);

    -- 4. 受攻击燃烧
    local burnId = getPropId("burn");
    trigger(target, propId, burnId, source);

    -- 5. 受攻击僵化
    local rigidId = getPropId("rigid");
    trigger(target, propId, rigidId, source);

    -- 6. 受攻击流血
    local bleedId = getPropId("bleed");
    trigger(target, propId, bleedId, source);

    -- 7. 受攻击净化
    propId = getPropId("wounded_purge");
    trigger(target, propId, 1, source);

    -- 8. 受攻击摄魔
    if source.type == OBJECT_TYPE_USER then
        propId = getPropId("wounded_cost_mp");
        trigger(target, propId, 1, source);
    end

    -- 7. 受攻击触发腐蚀
    local props = PropM.fetchProps(target, "corrosion_trigger");
    local prop;
    for _, prop in ipairs(props) do
        trigger(target, prop[1], prop[2], source);
    end
end

-- 判断是否免疫伤害
function isImmunity(source, target, skillId)
    if SkillM.immunitySkill(source, target, skillId) and
       FormulaM.invoke("CAN_TARGET_INJURED", target, skillId) then
        -- 免疫了技能
        return true;
    end

    local prop;
    local rand;
    if source.type == OBJECT_TYPE_SUMMON then
        -- 攻击方是召唤兽，判断是否免疫召唤兽伤害
        prop = PropM.combine(target, "summon_damage_immunity", 1);
        rand = DungeonM.getRandSeed("summon_damage_immunity") % 1000;
        if rand < prop[3] then
            return true;
        end
    end

    -- 只受到召唤兽伤害
    prop = PropM.combine(target, "summon_damage_limit", 1);
    if prop[3] > 0 then
        if source.type ~= OBJECT_TYPE_SUMMON then
            return true;
        end

        if source.classId ~= prop[3] then
            return true;
        end
    end

    -- 怪物、召唤兽均有该属性，故去除source类型的判断
    prop = PropM.combine(target, "damage_immunity", 1);
    rand = DungeonM.getRandSeed("damage_immunity") % 1000;
    if rand < prop[3] then
        local immunityTimes = target.dbase:query("immunity_times", 0);
        immunityTimes = immunityTimes + 1;
        local maxTimes = FormulaM.invoke("CALC_MAX_IMMUNITY_TIMES", target);

        -- 判断免疫次数是否已达上限(召唤兽没有免疫次数的限制)
        if immunityTimes <= maxTimes or target.type == OBJECT_TYPE_SUMMON then
            -- 怪物触发了免疫属性
            trace("CombatM", "(%s)触发了免疫属性。", target:getName());
            target.dbase:set("immunity_times", immunityTimes);
            return true;
        end
    end

    return false;
end

-- 受创
function receiveDamage(source, target, damage, skillId, extra)
    -- 能否受创
    if not FormulaM.invoke("CAN_TARGET_INJURED", target, skillId) then
        if not SkillM.getSequence(target) then
            SkillM.initSequence(source, target, skillId);
        end
        SkillM.getSequence(target):invincible(source, target, skillId);
        return false;
    end

    if not FormulaM.invoke("CAN_AUDIENCE_INJURED", target, skillId) then
        return false;
    end

    -- 如果免疫了该技能
    if isImmunity(source, target, skillId) then
        if not SkillM.getSequence(target) then
            SkillM.initSequence(source, target, skillId);
        end
        SkillM.getSequence(target):immunity(source, target, skillId);
        return 0;
    end

    local combine = PropM.combine;
    local PropM_apply = PropM.apply;
    local fetchProps = PropM.fetchProps;
    local trigger = PropM.trigger;

    -- 伤害增幅，加成为正，减少为负
    local addon = 0
    -- --------------- 计算source的prop影响下的总伤害 ---------------
    -- 给敌人造成绝对值伤害加成/减少
    local prop = combine(source, "enemy_damage2", 1);
    if target.type == OBJECT_TYPE_MONSTER then
        addon = addon + PropM_apply(prop, damage);
    end

    damage = damage + addon;
    -- --------------- 计算target的prop影响下的总伤害 ---------------
    -- --------------- 先百分比后绝对值，百分比计算时直接叠加 ---------------
    addon = 0;
    local props;

    -- 百分比伤害加成（削弱）
    props = fetchProps(target, "damage");
    for _, prop in ipairs(props) do
        damage = damage - PropM_apply(prop, damage);
    end

    -- 百分比伤害减少(有上限)
    props = fetchProps(target, "damage_by_floor");
    for _, prop in ipairs(props) do
        damage = damage - PropM_apply(prop, damage);
    end

    -- 对某一系敌人的伤害百分比减少(仅适用于玩家)
    if source.type == OBJECT_TYPE_MONSTER then
        local style = source.style;
        prop = combine(target, "monster_type_defense", style);
        damage = damage - PropM_apply(prop, damage);
    end

    -- 对BOSS的伤害百分比减少
    if source.isBoss and source:isBoss() then
        prop = combine(target, "reduce_boss_damage", 1);
        damage = damage - PropM_apply(prop, damage);
    end

    -- 生命低于一定百分比时降低所受的伤害
    props = fetchProps(target, "hp_to_absorb_damage");
    for _, prop in ipairs(props) do
        damage = damage - trigger(target, prop[1], prop[2], damage);
    end

    -- 战斗大师专属增加格挡减少%伤害(冈布奥专属)
    if source ~= target then
        props = fetchProps(target, "less_damage_hit");
        for _, prop in ipairs(props) do
            damage = trigger(target, prop[1], prop[2], {["damage"] = damage, ["source"] = source});
        end
    end

    -- 百分比伤害加成
    prop = combine(target, "receive_damage_add", 1);
    if PropM_apply(prop, 1) > 0 then
        damage = damage + trigger(target, prop[1], prop[2], damage);
    end

    -- 百分比伤害加成
    prop = combine(target, "mark_receive_damage_add", 1);
    if PropM_apply(prop, 1) > 0 then
        damage = damage + trigger(target, prop[1], prop[2], damage);
    end

    -- 龙威百分比伤害加成
    prop = combine(target, "dragon_majesty", 1);
    if PropM_apply(prop, 1) > 0 then
        damage = damage + trigger(target, prop[1], prop[2], damage);
    end

    -- 受到某类敌人攻击时，损失百分比的生命值
    local maxHp = target:queryAttrib("max_hp");
    prop = combine(target, "hurt_more_by_someone", source.type);
    damage = damage + PropM_apply(prop, maxHp);

    -- 绝对值伤害加成（削弱）
    prop = combine(target, "damage2", 1);
    addon = addon - PropM_apply(prop, damage);

    -- 绝对值伤害减少（有上限）
    props = fetchProps(target, "damage_by_floor2");
    for _, prop in ipairs(props) do
        addon = addon - PropM_apply(prop, damage);
    end

    -- 最终伤害，不能低于0
    damage = damage + addon;
    damage = math.max(damage, 0);
    -- --------------- 特殊，免疫伤害 ---------------
    -- 概率免疫伤害
    prop = combine(target, "probability_invincible", 1);
    if prop[3] > 0 then
        damage = trigger(target, prop[1], prop[2], damage);
    end

    if damage <= 0 then
        SkillM.initSequence(source, target, skillId);
        if skillId == PHYSIC_ATTACK then
            -- 普攻被格挡
            SkillM.getSequence(target):block(source, target, skillId);
        else
            -- 魔法免疫
            SkillM.getSequence(target):immunity(source, target);
        end

        return damage;
    end

    -- 抵挡并反弹伤害,只考虑玩家
    props = fetchProps(target, "deflect_damage");
    if #props > 0 and target.type == OBJECT_TYPE_USER and target ~= source then
        local deflectDamage = 0;
        for _, prop in ipairs(props) do
            deflectDamage = trigger(target, prop[1], prop[2], damage);
        end
        -- 抵挡并反弹
        damage = 0;

        -- 初始化动作序列
        SkillM.initSequence(target, source, skillId);
        doDamage(target, source, deflectDamage, skillId);

        return damage;
    end

    -- 转移伤害
    if (transferDamage(source, target, damage, skillId)) then
        return 0;
    end

    -- 转移部分伤害
    damage = transferPartDamage(source, target, damage, skillId);
    if damage <= 0 then
        return damage;
    end
    -- ------------------------- 反伤 ------------------------------------------------

    -- 物理反伤
    if skillId == PHYSIC_ATTACK and target.type == OBJECT_TYPE_MONSTER then
        local props = PropM.fetchProps(target, "reflex_physics_damage");
        local reflexDamage = 0;
        for _, prop in pairs(props) do
            reflexDamage = reflexDamage + trigger(target, prop[1], prop[2], damage);
        end

        if reflexDamage > 0 then
            trace("CombatM", "%s 反射了受到的物理伤害 %d。\n", target:getName(), reflexDamage);

            -- 初始化动作序列
            SkillM.initSequence(target, source, skillId);
            doDamage(target, source, reflexDamage, skillId);
        end
    end

    -- 伤害
    doDamage(source, target, damage, skillId, nil, extra);

    -- 对特殊的护盾造成伤害
    prop = combine(target, "special_shield", source.type);
    if prop[3] > 0 and not FormulaM.invoke("MONSTER_IS_TRANSED", target) then
        local shieldDamage = iif(prop[3] - damage >= 0, damage, prop[3]);
        PropM.record(target, {prop[1], prop[2], -shieldDamage, -1}, "special_shield");
    end
end

-- 转移伤害
function transferDamage(source, who, damage, skillId)
    if who.type ~= OBJECT_TYPE_USER and
        who.type ~= OBJECT_TYPE_MONSTER then
        return false;
    end

    -- 怪物不能转移技能伤害
    if skillId and skillId > 0 and who.type == OBJECT_TYPE_MONSTER then
        return false;
    end

    -- 自残不转移
    if source == who then
        return false;
    end

    -- 找到该层有“代替伤害”属性的怪物
    local found = false;

    local fetchProps = PropM.fetchProps;
    local combine = PropM.combine;
    local PropM_apply = PropM.apply;
    for pos = 1, DUNGEON_WIDTH * DUNGEON_HEIGHT do
        local grid = DungeonM.getGridByPos(pos);
        local target;
        local ok = true;

        if not grid:isOpened() then
            ok = false;
        elseif who.type == OBJECT_TYPE_USER then
            -- 如果不是召唤兽
            target = grid.summon;
            if not target or target:isDead() then
                ok = false;
            end
        elseif who.type == OBJECT_TYPE_MONSTER then
            -- 怪物
            target = grid.monster;
            if  not grid:isMonster() or
                not target or
                target:isDead() or
                target:query("base_class_id") == who:query("base_class_id") then
                ok = false;
            end
        end

        if target and #fetchProps(target, "replace_recv_damage") <= 0 then
            ok = false;
        end

        if ok then
            found = true;

            -- 反射转移伤害的属性
            local prop = combine(target, "reflex_transfer_damage", 1);
            local reflexDamage = PropM_apply(prop, damage);
            if  reflexDamage > 0 then
                if source.type == OBJECT_TYPE_MONSTER and source:query("monster_type") == MONSTER_TYPE_BOSS then
                    reflexDamage = math.modf(reflexDamage / 2);
                end
                -- 反射伤害
                CombatM.receiveDamage(target, source, reflexDamage, PHYSIC_ATTACK);
            end

            -- 降低转移伤害的属性
            prop = combine(target, "reduce_transfer_damage", 1);
            damage = PropM_apply(prop, damage);

            -- 先初始化动作序列
            SkillM.initSequence(source, target, skillId);

            -- 格挡光效
            if who.type == OBJECT_TYPE_MONSTER then
                -- 不需要击中光效
                who.noHitEffect = true;
                -- 怪物才播放格挡光效
                SkillM.getSequence(target):transfer(source, target, who);
                -- 需要延迟播放伤害
                target.delayDamageEffect = true;
            end

            doDamage(source, target, damage, skillId);
            break;
        end
    end

    return found;
end

-- 转移部分伤害
function transferPartDamage(source, who, damage, skillId)
    if who.type ~= OBJECT_TYPE_USER and
        who.type ~= OBJECT_TYPE_MONSTER then
        return damage;
    end

    -- 怪物不能转移技能伤害
    if skillId and skillId > 0 and who.type == OBJECT_TYPE_MONSTER then
        return damage;
    end

    -- 自残不转移
    if source == who then
        return damage;
    end

    local finallyDamage = damage;
    local fetchProps = PropM.fetchProps;
    local combine = PropM.combine;
    local PropM_apply = PropM.apply;
    for pos = 1, DUNGEON_WIDTH * DUNGEON_HEIGHT do
        local grid = DungeonM.getGridByPos(pos);
        local target;
        local ok = true;

        if not grid:isOpened() then
            ok = false;
        elseif who.type == OBJECT_TYPE_USER then
            -- 如果不是召唤兽
            target = grid.summon;
            if not target or target:isDead() then
                ok = false;
            end
        elseif who.type == OBJECT_TYPE_MONSTER then
            -- 怪物
            target = grid.monster;
            if  not grid:isMonster() or
                not target or
                target:isDead() or
                target:query("base_class_id") == who:query("base_class_id") then
                ok = false;
            end
        end

        if target and #fetchProps(target, "transfer_damage") <= 0 then
            ok = false;
        end

        if ok then
            -- 反射转移伤害的属性
            local prop = combine(target, "reflex_transfer_damage", 1);
            local reflexDamage = PropM_apply(prop, damage);
            if  reflexDamage > 0 then
                if source.type == OBJECT_TYPE_MONSTER and source:query("monster_type") == MONSTER_TYPE_BOSS then
                    reflexDamage = math.modf(reflexDamage / 2);
                end
                -- 反射伤害
                CombatM.receiveDamage(target, source, reflexDamage, PHYSIC_ATTACK);
            end

            -- 计算转移的伤害量
            prop = combine(target, "transfer_damage", 1);
            local transferDamage = PropM_apply(prop, damage);

            -- 降低转移伤害的属性
            prop = combine(target, "reduce_transfer_damage", 1);
            transferDamage = PropM_apply(prop, transferDamage);

            -- 先初始化动作序列
            SkillM.initSequence(source, target, skillId);

            -- 格挡光效
            if who.type == OBJECT_TYPE_MONSTER then
                -- 不需要击中光效
                who.noHitEffect = true;
                -- 怪物才播放格挡光效
                SkillM.getSequence(target):transfer(source, target, who);
                -- 需要延迟播放伤害
                target.delayDamageEffect = true;
            end

            -- 计算转移后剩下的伤害值、对替代怪造成伤害
            finallyDamage = finallyDamage - transferDamage;
            doDamage(source, target, transferDamage, skillId);
            break;
        end
    end

    return finallyDamage;
end

-- 最终伤害修正
function finalDamageFix(source, target, damage, skillId, extra)
    Profiler.funcBegin("finalDamageFix");

    -- 护盾守护
    damage = shieldProtect(target, damage);

    local combine = PropM.combine;
    local fetchProps = PropM.fetchProps;
    local trigger = PropM.trigger;
    local PropM_apply = PropM.apply;
    local prop;

    extra = extra or {};

    -- 非秒杀类伤害，计算减伤
    if extra["seckill"] ~= true then
        -- 判断是否伤害削减
        if not FormulaM.invoke("IS_IGNORE_DAMAGE_FIX", extra.assailantInfo) then
            -- 伤害削减至固定值
            prop = combine(target, "damage_fixed", 1);
            damage = PropM_apply(prop, damage);
        end

        -- 格挡伤害
        local props = fetchProps(target, "parry_damage");
        for _, prop in ipairs(props) do
            damage = trigger(target, prop[1], prop[2], damage);
        end
    end

    -- 对某种怪物伤害加成%
    props = fetchProps(source, "aid_damage");
    for _, prop in ipairs(props) do
        if trigger(source, prop[1], prop[2], target) then
            damage = damage * (1000 + prop[3]) / 1000;
        end
    end

    -- 降低主动技伤害(需要是可以免疫的技能)
    if SlimeSkillM.defyImmunitySkill(skillId) then
        prop = combine(target, "reduce_slime_skill_damage", 1);
        damage = damage - PropM_apply(prop, damage);
    end

    -- 斩杀效果类，伤害计算放在最后
    -- 攻击时附带斩杀效果
    if skillId == PHYSIC_ATTACK then
        -- 拔刀术专属技概率直接击杀
        props = fetchProps(source, "kill_probability");
        for _, prop in ipairs(props) do
            if trigger(source, prop[1], prop[2], target) then
                damage = target:getHp();
            end
        end

        -- 概率直接击杀BOSS
        props = fetchProps(source, "kill_probability2");
        for _, prop in ipairs(props) do
            if trigger(source, prop[1], prop[2], target) then
                damage = target:getHp();
            end
        end

        -- 攻击某类怪物直接击杀
        local style = target.style;
        if trigger(source, "monster_type_kill", style, target) then
            damage = target:getHp();
        end
    end

    -- 充能护盾
    prop = combine(target, "shield_power", 1);
    if prop[3] > 0 then
        -- 将所受伤害转换为恢复生命值
        local currHp = target:getHp();
        target:setRealHp(currHp + damage);

        EventMgr.fire(event.REFRESH_MONSTER_INFO, {});
        return 0;
    end

    Profiler.funcEnd("finalDamageFix");
    return damage;
end

-- 护盾保护
function shieldProtect(target, damage)
    Profiler.funcBegin("shieldProtect");

    -- 护盾状态
    local shieldStatus = CombatStatusM.getStatusByRule("shield");
    local reduceDamage = 0;

    for index = 1, #shieldStatus do
        local status = shieldStatus[index];
        local condition = CombatStatusM.queryCondition(target, status) or {};
        local shield = condition["shield"] or 0;

        if shield <= damage then
            -- 抵挡一部分伤害
            damage = damage - shield;
            reduceDamage = reduceDamage + shield;

            -- 这个状态耗光了
            condition["end_round"] = 0;
            condition["shield"]    = 0;
        else
            reduceDamage = reduceDamage + damage;

            -- 扣除护盾
            condition["shield"] = shield - damage;

            -- 伤害减到0
            damage = 0;

            break;
        end
    end

    if reduceDamage > 0 then
        -- 刷新下状态属性
        PropM.refreshEffect(target, "combat_status");

        -- 吸收伤害
        EventMgr.fire(event.USER_INFO_UPDATED, "receive_damage");
    end

    Profiler.funcEnd("shieldProtect");
    return damage;
end

-- 伤害
function doDamage(source, target, damage, skillId, drop, extra)
    Profiler.funcBegin("doDamage");

    Profiler.funcBegin("doDamage1");
    -- 默认是0
    skillId = skillId or 0;

    -- 能否受创
    if not FormulaM.invoke("CAN_TARGET_INJURED", target, skillId) then
        SkillM.initSequence(source, target, skillId);
        SkillM.getSequence(target):invincible(source, target, skillId);
        return false;
    end

    extra = extra or {};
    local assailantInfo = extra.assailantInfo;
    local delayPlay = extra.delayPlay;

    -- 如果有攻击者的信息，记录之
    if assailantInfo then
        target.dbase:setTemp("assailant_info", assailantInfo);
    else
        target.dbase:deleteTemp("assailant_info");
    end

    -- 修正下
    damage = finalDamageFix(source, target, damage, skillId, extra);

    -- 可能有忽视敌人减伤部分
    local baseClassId;
    if target.type == OBJECT_TYPE_MONSTER then
        baseClassId = target:query("base_class_id");
    end
    local reduceProp = PropM.combine(source, "ignore_reduce_damage", tonumber(baseClassId));
    -- 百分比伤害加成（削弱）
    local props = table.deepcopy(PropM.fetchProps(target, "reduce_damage"));
    for _, prop in ipairs(props) do
        prop[3] = prop[3] - reduceProp[3];
        damage = damage - PropM.apply(prop, damage);
    end

    if ME.user.dbase:queryTemp("delayHpEffect") then
        -- 延迟受创需要修正
        ME.user.dbase:setTemp("delayHpEffect", damage);
    end

    -- 记录下是谁干的
    target.attackerInfo = {
        ["source"]  = source,
        ["skillId"] = skillId,
        ["round"]   = getRound(),
    };

    -- 扣血
    target:receiveDamage(damage, delayPlay);
    Profiler.funcEnd("doDamage1");
    Profiler.funcBegin("doDamage2");

    -- 先初始化动作序列
    SkillM.initSequence(source, target, skillId);

    -- 如果是树屋攻击，延迟播放效果
    if source and source.isTreeHouse and source:isTreeHouse() then
        target.delayDamageEffect = 0.5;
        target.damageTimes = target.damageTimes and target.damageTimes + 1 or 1;
    end

    -- 某些技能表现需要延迟一下
    if type(extra["delay_time"]) == "number" then
        target.delayDamageEffect = extra["delay_time"];
        target.damageTimes = target.damageTimes and target.damageTimes + 1 or 1;
    end

    local fetchProps = PropM.fetchProps;
    local trigger = PropM.trigger;
    -- 死亡了
    local props, prop;
    local killed = false;
    if target:die() then
        -- 增加死亡动作
        SkillM.getSequence(target):die(source, target, damage, skillId);

        -- 杀敌奖励
        if source ~= target then
            killed = true;

            -- 1. 百分比
            props = fetchProps(source, "kill_recover")
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2]);
            end

            -- 2. 绝对值
            props = fetchProps(source, "kill_recover2");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2]);
            end

            -- 吟游诗人之歌
            props = fetchProps(source, "poet_song");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2]);
            end

            -- 3. 绝对值，与专属技区分
            props = fetchProps(source, "kill_recover3");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2]);
            end

            -- 4. 击杀有生命的敌人奖励
            props = fetchProps(source, "kill_living_recover");
            prop = PropM.combine(source, "use_to_lifeless", "kill_living_recover");
            if #props > 0 and FormulaM.invoke("IS_LIVING_TARGET", target) or prop[3] > 0 then
                for _, prop in ipairs(props) do
                    trigger(source, prop[1], prop[2]);
                end
            end

            -- 5. 绝对值
            props = fetchProps(source, "kill_up");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2]);
            end

            -- 杀敌附加buff
            props = fetchProps(source, "the_departed");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2], target);
            end

            -- 6.杀敌概率触发技能
            props = fetchProps(source, "kill_skill");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2], target);
            end

            -- 7.收集灵魂
            props = fetchProps(source, "soul_collect");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2], target);
            end

            -- 8.吞噬尸体
            props = fetchProps(source, "swallow_upgrade");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2], target);
            end

            -- 累积杀敌增加属性
            props = fetchProps(source, "kill_attrib");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2], target);
            end

            -- 累积杀敌增加属性（屠夫）
            props = fetchProps(source, "butcher_kill");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2], target);
            end

            -- 杀敌加探索点
            props = fetchProps(source, "target_sp");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2], target);
            end

            -- 击杀奖励物品
            props = fetchProps(source, "kill_get_item");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2], target);
            end

            -- 收集冈布奥主动技能量
            props = fetchProps(source, "kill_gain_attrib");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2]);
            end

            -- 增加装备经验
            props = fetchProps(source, "kill_add_exp");
            for _, prop in ipairs(props) do
                trigger(source, prop[1], prop[2], target);
            end

            -- 击杀概率拘禁灵魂
            prop = PropM.combine(source, "soul_detention", 1);
            if prop[3] > 0 then
                trigger(source, prop[1], prop[2], target);
            end

            -- 击杀概率拘禁灵魂
            prop = PropM.combine(source, "skill_get_soul", skillId);
            if prop[3] > 0 then
                trigger(source, prop[1], prop[2], target);
            end
        end

        if target.type == OBJECT_TYPE_MONSTER then
            -- TODO: 掉落
            target:dropProperty(source, drop);

            if not drop then
                local pos = target:getPos();
                local grid = DungeonM.getGridByPos(pos);
                local element = grid.element;
                local dbase = DungeonM.queryElement(element, "dbase") or {};

                -- 如果配置了死亡转换元素，执行转换元素
                if type(dbase["trans_element"]) == 'number' then
                    local toElement = dbase["trans_element"];
                    local toClass = DungeonM.queryElement(toElement, "class");
                    local para = { ["class"] = toClass, ["element"] = toElement };
                    DungeonM.transformGrid(toClass, pos, GRID_STATE_OPEN, para);
                end

                if dbase["set_progress"] == 1 then
                    UnrealDungeonM.setCurDungeonProgress();
                end
            end

            -- 死亡奖励
            prop = PropM.combine(target, "death_spoil", 1);
            if prop[3] > 0 then
                trigger(target, prop[1], prop[2]);
            end

            props = fetchProps(target, "death_bonus");
            for _, prop in ipairs(props) do
                trigger(target, prop[1], prop[2]);
            end
        end
    else
        -- 增加受创动作
        SkillM.getSequence(target):receiveDamage(source, target, damage, skillId, extra);
    end
    Profiler.funcEnd("doDamage2");
    Profiler.funcBegin("doDamage3");

    -- 如果是玩家受创，抛出伤害结果事件
    if target.type == OBJECT_TYPE_USER then
        EventMgr.fire(event.HERO_DAMAGED_RESULT, {["who"] = target, ["damage"] = damage, ["killed"] = killed, });
    end
    Profiler.funcEnd("doDamage3");
    Profiler.funcBegin("doDamage4");

    -- TODO: 尝试吸血,可以超过上限
    if  source.type == OBJECT_TYPE_SUMMON and
        source ~= target then
        local rate = source:getAbsorbRate();
        if DungeonM.getRandSeed("absorb_rate") % 100 < rate then
            local hp = math.max(damage, 0);

            source:addAttrib("hp", hp);
            -- source.dbase:set("hp", source.dbase:query("hp", 0) + hp);
        end

        PropM.trigger(source, "crit_absorb", 1, damage);
    end

    -- 泰坦护盾，尝试累积减伤率
    if source ~= target then
        props = fetchProps(target, "titan_shield")
        for _, prop in ipairs(props) do
            trigger(target, prop[1], prop[2]);
        end
    end

    -- 尝试恢复能量
    local damageType;

    if type(skillId) == "number" and SkillM.query(skillId) then
        -- 魔法攻击
        damageType = "magic";
    elseif skillId == PHYSIC_ATTACK then
        damageType = "attack";
    else
        -- 其他攻击
        damageType = "other";
    end

    props = fetchProps(target, "collect_energy");
    for _, prop in ipairs(props) do
        if damageType == prop[2] then
            trigger(target, prop[1], prop[2]);
        end
    end

    -- 杀敌概率触发召唤，之所以放到这里做，是因为召唤过程会把target析构掉
    -- 另外，怪物全清之后就不再召出召唤物，因为客户端翻开格子是定时一个一个翻开的，服务器是一下子就全翻了
    -- 导致这时候两边的可用格子不一致
    -- 为了不每次都去判断是否怪物已经全清，这里就不做判断了，在skill_summon、kill_summon2规则里面判断（因为毕竟小概率跑进去）
    if killed and target.type == OBJECT_TYPE_MONSTER and MonsterM.wouldKillTrigger(source, target) then
        local props = fetchProps(source, "kill_summon");
        for _, prop in ipairs(props) do
            trigger(source, prop[1], prop[2], target:getPos());
        end
        props = fetchProps(source, "kill_summon2");
        for _, prop in ipairs(props) do
            trigger(source, prop[1], prop[2], target:getPos());
        end
    end

    Profiler.funcEnd("doDamage4");
    Profiler.funcEnd("doDamage");
end

-- 受到治疗
function receiveCure(source, target, hp)
    -- 先初始化动作序列
    SkillM.initSequence(source, target, 0);

    -- 增加治疗动作
    SkillM.getSequence(target):receiveCure(source, target, hp);

    if target.type == OBJECT_TYPE_USER then
        BonusM.doBonus({ 2, "hp", hp, }, "receive_cure");
    elseif target.type == OBJECT_TYPE_MONSTER then
        local prop = PropM.combine(target, "blood_poisoned", 1);
        if prop[3] > 0 then
            hp = 0;
        end
        target:addAttrib("hp", hp);
    end

    local sourceName = source:getName();
    local targetName = target:getName();

    local msg = string.format("[CombatM]%s受到%s的%d点治疗，当前血量%d。", targetName, sourceName, hp, target:getHp());
    DungeonLogM.addLog(msg);
end

-- 根据格子种类筛选格子元素
function filterByClass(fighters, class)
    local ret = {};

    for _, fighter in pairs(fighters) do
        if fighter.class == class then
            table.insert(ret, fighter);
        end
    end

    return ret;
end

-- 根据类别筛选格子元素
function filterByType(fighters, type)
    local ret = {};

    for _, fighter in pairs(fighters) do
        if fighter.type == type then
            table.insert(ret, fighter);
        end
    end

    return ret;
end

-- 获取属性减少消耗
function getReduceAttrib(baseValue, attrib)
    -- 百分比属性消耗减少
    local prop = PropM.combine(ME.user, "cost", attrib);
    baseValue = PropM.apply(prop, baseValue);
    return baseValue;
end

-- 获取装备分解增加属性
function getDivideEquip(baseValue, attrib)
    -- 百分比属性增加属性
    local prop = PropM.combine(ME.user, "divide_equip", attrib);
    baseValue = PropM.apply(prop, baseValue);
    -- 绝对值属性增加属性
    prop = PropM.combine(ME.user, "divide_equip2", attrib);
    baseValue = PropM.apply(prop, baseValue);
    return baseValue;
end

-- 获取卷轴分解增加属性
function getDivideSpell(baseValue, attrib)
    -- 百分比属性增加属性
    local prop = PropM.combine(ME.user, "divide_spell", attrib);
    baseValue = PropM.apply(prop, baseValue);
    -- 绝对值属性增加属性
    prop = PropM.combine(ME.user, "divide_spell2", attrib);
    baseValue = PropM.apply(prop, baseValue);
    return baseValue;
end

-- 玩家出手回合回调
roundAction = function(index)
    Profiler.funcBegin("roundAction")
    -- TODO: 玩家技能触发先做
    SkillM.skillTrigger(ME.user);

    -- 开始下回合
    startRound();

    -- TODO: 回合开始时，有新的触发技能
    SkillM.skillTrigger(ME.user);

    -- 回合+1
    round = round + 1;

    -- 结束回合
    endRound();
    Profiler.funcEnd("roundAction")
end
